Register what a context declares, and stop counting the wizard scaffold - #55
Merged
Merged
Conversation
Both reported by @MBrekhof on 2026-08-27, found running `xaflogic wiki` over four of his own applications. Both reproduced on a probe fixture before a line was changed, and the second verified against the six real applications. Closes #53. The DbSet roster reached a context by walking base names from a set seeded with `DbContext` and grown only by classes declared in the analyzed source, so a base living in a package was never reached. `IdentityDbContext<T>` is the base every ASP.NET Core Identity template writes: an application with 56 `DbSet<T>` properties reported zero entities, silently, and announced itself as EF Core while doing it. A class that declares `DbSet<T>` properties is now a context whatever it derives from -- the stronger signal, and the only one that does not need the base class in hand. The base walk is deleted rather than kept alongside, because a class it found without `DbSet<T>` properties contributed nothing to the roster anyway. `LegacyRepository`, which declares `DbSet<T>` as a local inside a method body, is still not a registration. Closes #54. The XAF Project Wizard writes `ApplicationUser` and `ApplicationUserLoginInfo` into every solution created with v21.1 or later, so the rule that makes a class yours -- its own source was read here -- let them through, and any two XAF applications built since 2021 appeared to share them. A class is now shown as carried by the framework when every application declares it with a DevExpress security contract AND with the same properties, and it is left out of the count, the map, the overlap grid and the vocabulary. Both halves are load-bearing, and the real corpus proves it: `ApplicationUser` is in four of the six and still counts, because pwLegalOffice added `Email`, `Photo` and `Tasks`. Only `ApplicationUserLoginInfo` drops out, 17 to 16. Three further consumers of that number were wrong with the suite green, and all three were found by generating the page and reading it rather than by running tests. The CLI summary printed the unfiltered count, so the terminal and the page it had just written disagreed. The conventions card offered `LoginProviderName` and `ProviderUserKey` as house vocabulary to two applications sharing nothing else -- excluding them promoted three real house names into a list capped at sixty. And `WriteSites` bypassed the helper that pluralises, printing `1 properties` beside a heading that got it right; the helper comment already says why that matters. Unrelated and included because it obstructs everyone: `EntityAnalyzer.cs` carried a literal NUL byte inside a string interpolation, used as a composite key separator. It compiled, and it made the largest file in the project binary to grep and ripgrep, which refuse to search it and say only "binary file matches". Written as the escape instead, which is the identical character. 525 tests, up from 514. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W45tzJFX3NoSrk7svtQeKT
This was referenced Sep 6, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #53 and #54, both filed by @MBrekhof on 2026-08-27 from a
xaflogic wikirun over four of his own applications. Both were reproduced on a throwaway fixture before a line was changed, and the second was checked against the six real applications rather than against a fixture that would have agreed with whatever the code already did.#53 — a context on a package base registered nothing at all
DbSetRoster.FindContextClassesreached a context by walking base names from a set seeded withDbContextand grown only by classes declared in the analyzed source. A base that lives in a package is never declared here, so it was never reached — and the roster is the only way a plain EF Core entity with no XAF base is accepted at all.The everyday case is
IdentityDbContext<TUser>, the base every ASP.NET Core Identity template writes:Worse than the issue reports it: the ORM was still announced as EF Core, so the answer was not "I could not tell" but "this is an EF Core application with no entities", said in the same voice as everything that was actually read.
Fixed with the first of the three shapes the issue proposes: a class that declares
DbSet<T>properties is a context, whatever it derives from. That is the stronger signal — it is the application stating a table, it has to be right for the application to run, and it is the only one that does not need the base class in hand.The base walk is deleted rather than kept alongside, because it was exactly redundant: a class it found without
DbSet<T>properties contributed nothing to the roster anyway. A hand-writtenAuditedDbContext : DbContextand a context on a package base stop being two cases, one of which worked.It stays a property, never a mention of the generic. The existing
LegacyRepositoryfixture — which writesDbSet<AuditEntry>as a local inside a method body — still contributes nothing, and its test still passes untouched.New fixture
ProfileDbContext.cs: a context whose base and its type argument are declared nowhere in the fixture, which is the shape both existing roster fixtures already passed.#54 — the wizard scaffold counted as classes you had modelled twice
The rule that makes a class yours — its own source was read in one of the projects — is the right rule, and it is exactly why the scaffold gets in. The Project Wizard writes
ApplicationUserandApplicationUserLoginInfointo every solution created with v21.1 or later, so their source is read.Two probe applications sharing nothing but their author reported
recurring=2.A class is now shown as carried by the framework when every application declares it with a DevExpress security contract and with the same properties, and it is then left out of the count, the map, the overlap grid and the vocabulary. It is still on the page, under its own heading, with the contract that earned it the label printed on the card so a reader can disagree.
Both halves are load-bearing, and the six real applications prove the second one rather than a fixture asserting it:
ApplicationUserEmail,Photo,Tasks— shapes differApplicationUserLoginInfoClasses modelled more than once: 17 → 16. Exactly one class moved, and it is the right one. A developer who extends the scaffold really has built something, and that difference is what the property comparison already exists to show.On the name list
The issue argues this avoids the kind of list the wiki refuses to keep. That is half true and worth saying out loud: the rule still needs to know which interfaces are DevExpress security contracts. It is a different kind of list — framework API surface that DevExpress documents and cannot rename without breaking every application implementing it, rather than the class names one template happened to emit this year — but it is a list, and the code says so where it defines it. The names were checked against the DevExpress documentation, not written from memory.
It also means a class is never dismissed on the contract alone: the documented
Employee : Person, ISecurityUser, ...shape is a business object that happens to log in, and it keeps counting the moment two applications shape it differently.Three more consumers of that number were wrong, with the suite green
All three found by generating the page and reading it. None of them would have failed a test.
LoginProviderNameandProviderUserKeywere house vocabulary — the same false claim as the classes card, two headings down. A class that does not count as modelled twice no longer supplies words that do. On the real corpus this promoted three actual house names into a list capped at sixty.WriteSitesbypassed the helper that pluralises, printing1 propertiesbeside a heading that got it right. That helper comment already says why it exists: "entity" reaching the page as "entitys" is the kind of thing a reader takes as evidence that nobody looked. Four call sites went around it.One unrelated fix, included because it obstructs everyone
EntityAnalyzer.cscarried a literal NUL byte inside a string interpolation, used as a composite key separator. It compiled, and it made the largest file in the project binary togrepandripgrep, which refuse to search it and report onlybinary file matches. It blocked two searches while this work was being done. Written as the\0escape instead — the identical character, and the file is searchable again.Verification
One thing for the reviewer
This is one commit for two issues. They came from the same report and land together, but they split cleanly into three (#53, #54, and the NUL) with each one green on its own — including the README test count, which steps 514 → 515 → 525 in that order. Say the word and I will rearrange it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01W45tzJFX3NoSrk7svtQeKT